from PyQt6.QtWebSockets import QWebSocketServer
from PyQt6.QtCore import QRunnable, QObject
from PyQt6.QtNetwork import QAbstractSocket
import json

port = 12345  # 服务端端口号。

def get_local_ip():
    """
    获取本机在局域网中的 IP 地址（通常为 192.168.x.x 或 10.x.x.x）。
    用于提示开发者服务端可能可通过该 IP 被同一网络下的其他设备访问。
    如果获取失败（如无网络），则返回默认的本地回环地址 127.0.0.1。
    """
    import socket
    try:
        # 创建一个 UDP 套接字（不需要真正发送数据包）。
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 连接到公共 DNS 服务器（8.8.8.8:80），目的是让系统分配一个本地出口 IP。
        s.connect(("8.8.8.8", 80))
        # 获取套接字本地绑定的 IP 和端口，即本机用于外发的 IP 地址。
        ip = s.getsockname()[0]
        s.close()
        return ip
    except Exception:
        # 如果出现任何异常（如无网络），返回本地回环地址。
        return "127.0.0.1"

class WorkerSignals(QObject):
    from PyQt6.QtCore import pyqtSignal
    finished = pyqtSignal()
    result = pyqtSignal(object)
    error = pyqtSignal(Exception)

class MyRunnableTask(QRunnable):
    def __init__(self, task_data):
        super().__init__()
        self.task_data = task_data
        self.signals = WorkerSignals()

    def run(self):
        # 这里放你的耗时任务，比如访问网络、计算等。
        result = f"处理数据：{self.task_data}，耗时操作完成。"
        self.signals.result.emit(result)
        self.signals.finished.emit()

class WebSocketServer(QWebSocketServer):
    """
    非加密模式。尝试监听回环地址。服务端功能：
    1，被动接受客户端连接。
    2，接收客户端发送的消息。包括登录时用户名获取（login类型），转发或者发送消息给客户端（chat类型）。
    3，手动控制，或者自动接受客户端断开连接。
    4，手动发送或者自动转发消息给客户端（tip或者chat类型）。
    """
    def __init__(self):
        from PyQt6.QtNetwork import QHostAddress
        from PyQt6.QtCore import QThreadPool
        super().__init__("WebSocket Server", QWebSocketServer.SslMode.NonSecureMode)
        self.thread_pool = QThreadPool.globalInstance()  # 使用全局线程池。
        self.thread_pool.setMaxThreadCount(10)  # 最多10个线程同时运行。
        self.clients = {}  # 使用字典存储客户端，键为客户端用户名，值为QWebSocket对象。
        if self.listen(QHostAddress.SpecialAddress.Any, port):
            print(f"✅ WebSocket Server 启动成功，监听地址：ws://127.0.0.1:{port}")
        else:
            print("❌ WebSocket Server 启动失败，请检查端口是否被占用或程序权限。")
        self.newConnection.connect(self.on_new_connection)
        self.window: ServerWindow = None  # 窗口对象。

    def on_new_connection(self):
        """
        新的客户端连接，但还没有发送用户名。
        """
        client = self.nextPendingConnection()
        # ✅ 绑定信号 —— 只绑定一次，无需后续重新绑定。
        client.textMessageReceived.connect(lambda msg, c=client: self.on_message_received(msg, c))
        print("🔌 新客户端已连接（尚未登录）")

    def on_message_received(self, message: str, client):
        """
        接收客户端消息。并解析json格式的数据。
        1，客户端连接时会发送包含username的消息，服务端注册并发送注册成功或失败消息给客户端。
        2，客户端不定时需要发送数据给指定的用户，由服务端转发。
        :param message: 需要解析的json格式的字符串。
        :param client: QWebSocket对象。
        """
        data = json.loads(message)  # 尝试解析为 JSON。
        del message
        # --- 处理登录消息 ---
        if data['type'] == 'login':
            username = data["username"].strip()
            # 检查是否已经登录。
            if username in self.clients:
                print(f"❌ 用户名 {username} 已被使用。")
                self.send_to_client(username, "tip", message=f"用户名 {username} 已被使用。")
                username = f'user{len(self.clients)}'  # 自动生成用户名。
                self.send_to_client(username, "tip", message=f"用户名已被修改为 {username} 。")
            # ✅ 登录成功：将 client 以 username 为 key 存入 clients。
            self.clients[username] = client
            client.username = username  # 防止用户名被修改。
            print(f"✅ 客户端登录成功，username: {username}")
            self.send_to_client(username, "tip", message=f"用户 {username} 连接成功。")  # 发送消息提示客户端。
            client.disconnected.connect(lambda username=username: self.on_client_disconnected(username))
            # 如果你有 UI 类，可以在这里调用，例如：
            self.window.update_clients_list(list(self.clients.keys()))
            self.window.display_message(f"✅ 新用户 {username} 已连接。")
            # 注意：不需要重新绑定信号，因为已经在 on_new_connection 绑定过了。
        elif data["type"] == "chat":
            from_username = data['from_username']
            to_username = data['to_username']
            content = data['content']
            if not to_username:
                self.window.display_message(
                    f"📤 来自用户 {from_username} 的消息：{content}")  # 默认由服务端接收后显示。
            elif to_username not in self.clients:
                self.send_to_client(from_username, "tip", message=f'用户 {to_username} 不存在。')  # 提示消息。
            else:
                self.send_to_client(to_username, "chat", 
                                    from_username=from_username, message=content)  # 由服务端转发。
        else:
            print(f"ℹ️ 收到未知 JSON 消息，内容：{data}")

    def on_client_disconnected(self, username: str):
        """
        客户端断开服务端，或者服务端控制客户端断开。
        :param username: 客户端的用户名。
        """
        try:
            if self.clients[username].state() == QAbstractSocket.SocketState.ConnectedState:
                self.clients[username].close()  # 服务端控制客户端断开。
                return
        except KeyError:
            self.window.display_message(f"❌ 用户 {username} 未找到。")
            print(f"👋 断开连接的客户端未在已登录列表中找到。")
        else:
            del self.clients[username]  # 无论服务端断开客户端与否，客户端的槽都会触发。
            print(f"👋 用户 {username} 已断开")
            self.window.display_message(f"❌ 用户 {username} 已断开。")
            self.window.update_clients_list(list(self.clients.keys()))

    def send_to_client(self, client_id: str, message_type: str, **context):
        """
        新建线程，发送消息给客户端。
        :param client_id: 客户端的用户名。
        :param message_type: 消息类型。
        :param context: 发送给客户端的消息。
        """
        task = MyRunnableTask(context)  # 线程发送消息。
        if client_id in self.clients:
            client = self.clients[client_id]
            if client.state() == QAbstractSocket.SocketState.ConnectedState:
                json_message = {"type": message_type, "context": context["message"]}
                if message_type == "chat":
                    json_message["from_username"] = context["from_username"]  # 服务端转发消息来源。
                message = json.dumps(json_message, ensure_ascii=False)
                # client.sendTextMessage(message)
                task.signals.result.connect(lambda : client.sendTextMessage(message))  # 线程发送消息。
                self.thread_pool.start(task)  # 线程发送消息。
                self.window.display_message(f"📤 服务端已发送消息给用户 {client_id}: {context["message"]}")
            else:
                self.window.display_message(f"❌ 用户 {client_id} 已断开，无法发送消息。")
        else:
            self.window.display_message(f"❌ 用户 {client_id} 未找到。")

class ServerWindow:
    """
    服务端窗口功能：
    1，每20s清空一次消息显示屏。
    2，发送消息给指定客户端。
    3，显示连接的客户端的列表。
    4，接收并显示客户端消息，显示发送给客户端的消息。
    """
    def __init__(self):
        from PyQt6.QtWidgets import (QMainWindow, QTextEdit, QComboBox, QLineEdit, QPushButton)
        from PyQt6.QtCore import QTimer
        from PyQt6.uic.load_ui import loadUi
        ui: QMainWindow = loadUi('./server.ui')
        self.show = ui.show
        self.text_display: QTextEdit = ui.text_display  # 消息显示区。
        self.client_combo: QComboBox = ui.client_combo  # 客户端选择。
        self.input_message: QLineEdit = ui.input_message  # 输入框。
        send_button: QPushButton = ui.send_button  # 发送按钮。
        send_button.clicked.connect(self.on_send_clicked)
        disconnect_button: QPushButton = ui.disconnect_button  # 断开客户端连接按钮。
        disconnect_button.clicked.connect(self.disconnect_client)
        # 创建并绑定服务端。
        self.server = WebSocketServer()
        self.server.window = self  # 让服务端知道它的 UI 窗口是谁。
        # 获取本地ip。
        ip = get_local_ip()
        self.text_display.setPlaceholderText(f"本机（{ip}: {port}）。")
        # 设置30s清理一次消息显示。
        self.timer = QTimer()
        self.timer.setInterval(30000)
        self.timer.start(30000)
        self.timer.timeout.connect(lambda: self.text_display.clear())

    def update_clients_list(self, clients: list):
        """
        更新客户端列表。
        :param clients: 保存所有客户端的用户名的列表。
        """
        self.client_combo.clear()
        self.client_combo.addItems(clients)

    def disconnect_client(self):
        """
        手动断开客户端的连接。
        """
        selected_client = self.client_combo.currentText()
        self.server.on_client_disconnected(selected_client)

    def on_send_clicked(self):
        """
        发送消息给指定客户端。
        """
        selected_client = self.client_combo.currentText()
        msg = self.input_message.text().strip()
        if not selected_client or not msg:
            return
        self.server.send_to_client(selected_client, "tip", message=msg)
        self.input_message.clear()

    def display_message(self, message: str):
        """
        显示消息到消息显示区。
        :param message: 消息。
        """
        self.text_display.append(message)

if __name__ == '__main__':
    import sys
    from PyQt6.QtWidgets import QApplication
    app = QApplication(sys.argv)
    window = ServerWindow()
    window.show()
    sys.exit(app.exec())